package top.jiangqiang.qianyi.common.filter.caching.wrapper;

import jakarta.servlet.http.HttpServletRequest;
import lombok.Getter;
import org.dromara.hutool.core.io.IoUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import top.jiangqiang.qianyi.common.exception.Error400Exception;

import java.io.IOException;
import java.util.stream.Stream;

public class CachingHttpServletRequestWrapper extends CachingRWrapper {

    /**
     * 默认最大缓存字节数量
     */
    public static final int DEFAULT_MAX_CACHE_LIMIT = 2 * 1024 * 1024;

    /**
     * 支持缓存的ContentType
     */
    private static final String[] SUPPORTED_CONTENT_TYPES = {
            MediaType.TEXT_MARKDOWN_VALUE,
            MediaType.TEXT_XML_VALUE,
            MediaType.TEXT_PLAIN_VALUE,
            MediaType.APPLICATION_JSON_VALUE,
            MediaType.APPLICATION_XML_VALUE,
            MediaType.APPLICATION_FORM_URLENCODED_VALUE,
            MediaType.APPLICATION_NDJSON_VALUE
    };
    /**
     * 支持缓存的请求方式
     */
    private static final HttpMethod[] SUPPORTED_HTTP_METHODS = {
            HttpMethod.GET,
            HttpMethod.POST,
            HttpMethod.PUT,
            HttpMethod.PATCH,
            HttpMethod.DELETE};

    private final byte[] data;
    /**
     * 是否已经缓存
     */
    @Getter
    private final boolean cached;

    /**
     * 默认限制缓存100*1024
     *
     * @param request
     * @throws IOException
     */
    public CachingHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        this.cached = requiresCaching(request, DEFAULT_MAX_CACHE_LIMIT);
        this.data = this.cached ? IoUtil.readBytes(request.getInputStream()) : new byte[0];
    }

    /**
     * @param request
     * @param maxCacheLimit 自定义缓存长度限制
     * @throws IOException
     */
    public CachingHttpServletRequestWrapper(HttpServletRequest request, int maxCacheLimit) throws IOException {
        super(request);
        this.cached = requiresCaching(request, maxCacheLimit);
        this.data = this.cached ? IoUtil.readBytes(request.getInputStream()) : new byte[0];
    }

    /**
     * 判断是否需要缓存
     *
     * @param request
     * @param maxCacheLimit 最大缓存长度，为负数则不缓存
     * @return
     */
    private boolean requiresCaching(HttpServletRequest request, int maxCacheLimit) {
        if (maxCacheLimit <= 0) {
            return false;
        }
        String method = request.getMethod().toUpperCase();
        if (Stream.of(SUPPORTED_HTTP_METHODS).noneMatch(httpMethod -> httpMethod.matches(method))) {
            return false;
        }

        String contentType = request.getContentType();
        //只缓存指定请求方式的指定contentType类型
        if (contentType == null || Stream.of(SUPPORTED_CONTENT_TYPES).noneMatch(s -> StrUtil.startWithIgnoreCase(contentType, s))) {
            return false;
        }

        int contentLength = request.getContentLength();
        if (contentLength == -1) {
            //长度未知或长度大于Integer.MAX_VALUE
            return false;
        }

        //长度大于缓存限制
        if (contentLength > maxCacheLimit) {
            throw new Error400Exception("请求路径：{} 请求体字节数：{} 超出缓存上限：{}", request.getRequestURL(), contentLength, maxCacheLimit);
        }
        return true;
    }

    /**
     * 如果没有被缓存，说明数据较大，不应该直接读取到内存，因此抛出异常
     *
     * @return
     */
    public byte[] getContentAsByteArray() {
        if (isCached()) {
            return this.data;
        } else {
            throw new UnsupportedOperationException();
        }
    }

}